package com.frinder.cglib;

import com.google.common.base.CaseFormat;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Created by frinder on 2017/10/16.
 */
@Slf4j
@Component
public class AdvisorTemplate {

    /**
     * 缓存
     */
    public static final Map<String, Method> METHODS = Maps.newConcurrentMap();

    /**
     * cache
     */
    public static final Map<String, String> CACHE = Maps.newConcurrentMap();

    @Autowired
    private ReflectTemplate reflectTemplate;

    @Autowired
    private JdbcTemplate jdbcTemplate;


    /**
     * add one object multiple properties
     *
     * @param extraProperties
     * @param dest
     * @return
     */
    public Object getTarget4MultipleProperty(ExtraProperties extraProperties, Object dest) {
        Map<String, Object> addProperties = Maps.newHashMap();
        ExtraProperty[] properties;
        if (null != (properties = extraProperties.extraProperties()) && properties.length > 0) {
            for (ExtraProperty extraProperty : properties) {
                addExtraProperty(extraProperty, dest, addProperties);
            }
        }
        Object target = reflectTemplate.getTarget(dest, addProperties);
        return target;
    }

    /**
     * 添加单个对象单个属性
     *
     * @param dest
     * @return
     */
    public Object getTarget(ExtraProperty extraProperty, Object dest) {
        Map<String, Object> addProperties = Maps.newHashMap();
        addExtraProperty(extraProperty, dest, addProperties);
        Object target = reflectTemplate.getTarget(dest, addProperties);
        return target;
    }

    /**
     * add extra property to map
     *
     * @param extraProperty
     * @param dest
     * @param addProperties
     */
    private void addExtraProperty(ExtraProperty extraProperty, Object dest, Map<String, Object> addProperties) {
        String v = extraProperty.value();
        if (!Strings.isNullOrEmpty(extraProperty.refSid())
                && !Strings.isNullOrEmpty(extraProperty.tableName())) {
            try {
                String sid = getSid(dest, extraProperty.refSid());
                String key = getKey(extraProperty, sid);
                if (!CACHE.containsKey(key)) {
                    List<Map<String, Object>> sidNames = getValues(extraProperty, sid);
                    if (null != sidNames && !sidNames.isEmpty()) {
                        CACHE.put(key, String.valueOf(sidNames.get(0).get(extraProperty.name())));
                    } else {
                        CACHE.put(key, "");
                    }
                }
                v = CACHE.get(key);
                if (Strings.isNullOrEmpty(v)) v = extraProperty.value();
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        addProperties.put(extraProperty.name(), v);
    }


    /**
     * add list object multiple properties
     *
     * @param list
     * @return
     */
    public List<Object> getTargetList4MultipleProperty(ExtraProperties extraProperties, List<Object> list) {
        if (null == list) return list;
        ExtraProperty[] properties;
        if (null != (properties = extraProperties.extraProperties()) && properties.length > 0) {
            for (ExtraProperty extraProperty : properties) {
                getSids(extraProperty, list);
            }
        }
        return list.stream().map(item ->
                getTarget4MultipleProperty(extraProperties, item)
        ).collect(Collectors.toList());
    }

    /**
     * 添加集合对象单个属性
     *
     * @param list
     * @return
     */
    public List<Object> getTargetList(ExtraProperty extraProperty, List<Object> list) {
        if (null == list) return list;
        getSids(extraProperty, list);
        return list.stream().map(item ->
                getTarget(extraProperty, item)
        ).collect(Collectors.toList());
    }

    /**
     * @param sidStr
     * @return
     */
    private List<Map<String, Object>> getValues(ExtraProperty extraProperty, String sidStr) {
        String selectSql = String.format(
                "SELECT %s AS %s, %s AS %s FROM %s WHERE %s IN ( ? )",
                extraProperty.refFieldName(), extraProperty.refSid(),
                extraProperty.fieldName(), extraProperty.name(),
                extraProperty.tableName(), extraProperty.refFieldName()
        );
        log.info("{}, {}", selectSql, sidStr);
        return jdbcTemplate.queryForList(selectSql, sidStr);
    }

    /**
     * 获取 sids
     *
     * @param list
     */
    private void getSids(ExtraProperty extraProperty, List<Object> list) {
        Set<String> sids = list.stream().filter(s -> {
            try {
                String sid = getSid(s, extraProperty.refSid());
                String key = getKey(extraProperty, sid);
                return !CACHE.containsKey(key);
            } catch (Exception e) {
                return false;
            }
        }).map(item -> {
            try {
                String sid = getSid(item, extraProperty.refSid());
                return sid;
            } catch (Exception e) {
                return null;
            }
        }).collect(Collectors.toSet());
        String sidStr = Joiner.on(",").skipNulls().join(sids);
        if (Strings.isNullOrEmpty(sidStr)) return;
        List<Map<String, Object>> sidNames = getValues(extraProperty, sidStr);
        if (null != sidNames && !sidNames.isEmpty()) {
            sidNames.stream().forEach(item -> {
                Object obj;
                if (null != (obj = item.get(extraProperty.refSid()))) {
                    String key = getKey(extraProperty, String.valueOf(obj));
                    CACHE.put(key, String.valueOf(item.get(extraProperty.name())));
                }
            });
        }
    }

    /**
     * 获取 key
     *
     * @param v
     * @return
     */
    private String getKey(ExtraProperty extraProperty, String v) {
        return String.format("%s-%s-%s-%s", extraProperty.tableName(), extraProperty.name(), extraProperty.refSid(), v).toLowerCase();
    }

    /**
     * 从返回值中获取 sid值
     *
     * @param dest
     * @param refSid
     * @return
     * @throws Exception
     */
    private String getSid(Object dest, String refSid) throws Exception {
        String get = String.format("%s%s", "get", CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, refSid));
        String methodKey = String.format("%s_%s", dest.getClass().getName(), get);
        Method method;
        log.info("get : {}", methodKey);
        if (METHODS.containsKey(methodKey)) {
            method = METHODS.get(methodKey);
        } else {
            method = dest.getClass().getMethod(get);
            METHODS.put(methodKey, method);
        }
        return String.valueOf(method.invoke(dest));
    }


    public static void main(String[] args) {
        String refSid = "holder_sid";
        String get = String.format("%s%s", "get", CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, refSid));
        System.out.println(get);
        System.out.println(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, refSid));
        System.out.println(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, refSid));
    }

}
